package cn.maiyar.swagger.config;

import cn.maiyar.swagger.SwaggerProperties;
import cn.maiyar.swagger.utils.SwaggerUtils;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.*;

/**
 * swagger配置
 *
 * @author c.c
 */
@Slf4j
@Configuration(proxyBeanMethods = false)
@EnableOpenApi
@EnableKnife4j
@AllArgsConstructor
@EnableConfigurationProperties(SwaggerProperties.class)
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerAutoConfiguration {

    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");

    private static final String DEFAULT_BASE_PATH = "/**";

    // 引入Knife4j扩展类
    private final OpenApiExtensionResolver openApiExtensionResolver;

    @Bean
    public Docket init(SwaggerProperties sp) {
        // 不存在解析的url规则,加入默认url规则
        if (sp.getBasePath().size() == 0) {
            sp.getBasePath().add(DEFAULT_BASE_PATH);
        }
        // 加入默认排除的url规则
        sp.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);

        ApiSelectorBuilder apis = new Docket(DocumentationType.OAS_30)
                .host(sp.getHost())
                .apiInfo(apiInfo(sp)).select()
                .apis(SwaggerUtils.basePackages(sp.getBasePackages()));

        sp.getBasePath().forEach(p -> apis.paths(PathSelectors.ant(p)));
        sp.getExcludePath().forEach(p -> apis.paths(PathSelectors.ant(p).negate()));
        return apis.build()
                .enable(sp.isEnable())
                .securitySchemes(Collections.singletonList(securitySchema(sp)))
                .securityContexts(Lists.newArrayList(securityContext(sp)))
                .extensions(openApiExtensionResolver.buildExtensions(sp.getServiceName()));
    }

    /**
     * 配置默认的全局鉴权策略的开关，通过正则表达式进行匹配；默认匹配所有URL
     *
     * @return
     */
    private SecurityContext securityContext(SwaggerProperties swaggerProperties) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth(swaggerProperties))
                .forPaths(PathSelectors.regex(swaggerProperties.getAuthorization().getAuthRegex()))
                .build();
    }

    /**
     * 默认的全局鉴权策略
     *
     * @return
     */
    private List<SecurityReference> defaultAuth(SwaggerProperties swaggerProperties) {
        ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swaggerProperties.getAuthorization().getAuthorizationScopeList().forEach(authorizationScope -> authorizationScopeList.add(new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[authorizationScopeList.size()];
        return Collections.singletonList(SecurityReference.builder()
                .reference(swaggerProperties.getAuthorization().getName())
                .scopes(authorizationScopeList.toArray(authorizationScopes))
                .build());
    }

    private OAuth securitySchema(SwaggerProperties swaggerProperties) {
        ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swaggerProperties.getAuthorization().getAuthorizationScopeList().forEach(authorizationScope -> authorizationScopeList.add(new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));

        ArrayList<GrantType> grantTypes = new ArrayList<>();
        swaggerProperties.getAuthorization().getTokenUrlList().forEach(tokenUrl -> grantTypes.add(new ResourceOwnerPasswordCredentialsGrant(tokenUrl)));
        return new OAuth(swaggerProperties.getAuthorization().getName(), authorizationScopeList, grantTypes);
    }

    private ApiInfo apiInfo(SwaggerProperties sp) {
        return new ApiInfoBuilder()
                .title(sp.getTitle())
                .description(sp.getDescription())
                .license(sp.getLicense())
                .licenseUrl(sp.getLicenseUrl())
                .termsOfServiceUrl(sp.getTermsOfServiceUrl())
                .contact(new Contact(sp.getContact().getName(), sp.getContact().getUrl(), sp.getContact().getEmail()))
                .version(sp.getVersion())
                .build();
    }
}
